﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using DirectShowLib;
using DirectShowLib.DES;
using System.Runtime.InteropServices;

namespace MediaLibraryManager
{
	public class MediaInfo
	{
		public string FileName { get; set; }
		public Guid AudioSybType { get; set; }
		public int Channels { get; set; }
		public float SamplesPerSec { get; set; }
		public int BitsPerSample { get; set; }
		public TimeSpan AudioLength { get; set; }
		public Guid VideoSubType { get; set; }
		public short ImageWidth { get; set; }
		public short ImageHeight { get; set; }
		public int BitsPerPixel { get; set; }
		public string FourCC { get; set; }
		public TimeSpan VideoLength { get; set; }
		public Bitmap Thumbnail { get; set; }

		public MediaInfo(string fileName)
		{
			Init(fileName,0);
		}

		public MediaInfo(string fileName,int thumbnailWidth)
		{
			Init(fileName,thumbnailWidth);
		}

		private void Init(string fileName,int thumbnailWidth)
		{
			int hr = 0;
			IMediaDet mediaDet = null;

			try
			{
				// Create the DirectShow's MediaDet
				mediaDet = (IMediaDet)new MediaDet();

				hr = mediaDet.put_Filename(fileName);
				if (hr < 0)
				{
					throw new NotSupportedException("This file is not supprted by MediaDet");
					//DsError.ThrowExceptionForHR(hr);
				}

				this.FileName = fileName;

				int streamCount;
				hr = mediaDet.get_OutputStreams(out streamCount);
				DsError.ThrowExceptionForHR(hr);

				for (int i = 0; i < streamCount; i++)
				{
					hr = mediaDet.put_CurrentStream(i);
					DsError.ThrowExceptionForHR(hr);

					Guid streamType;
					hr = mediaDet.get_StreamType(out streamType);
					DsError.ThrowExceptionForHR(hr);

					if (streamType == DirectShowLib.MediaType.Audio)
						UpdateAudioPart(mediaDet,this);
					else if (streamType == DirectShowLib.MediaType.Video)
						UpdateVideoPart(mediaDet,this);
					else
						continue;
				}

				if (this.VideoSubType != Guid.Empty && thumbnailWidth > 0)
					this.Thumbnail = GetSnapshot(
						mediaDet,
						thumbnailWidth > 0 ? thumbnailWidth : this.ImageWidth,
						thumbnailWidth > 0 ? thumbnailWidth * this.ImageHeight / this.ImageWidth : this.ImageHeight,
						this.VideoLength.TotalSeconds / 2);
			}
			finally
			{
				if (mediaDet != null)
					Marshal.ReleaseComObject(mediaDet);
			}
		}

		private static void UpdateAudioPart(IMediaDet mediaDet,MediaInfo mediaInfo)
		{
			int hr = 0;
			AMMediaType mediaType = new AMMediaType();

			hr = mediaDet.get_StreamMediaType(mediaType);
			DsError.ThrowExceptionForHR(hr);

			mediaInfo.AudioSybType = mediaType.subType;

			double streamLength;
			hr = mediaDet.get_StreamLength(out streamLength);
			DsError.ThrowExceptionForHR(hr);

			mediaInfo.AudioLength = TimeSpan.FromSeconds(streamLength);

			if (mediaType.formatType == FormatType.WaveEx)
			{
				WaveFormatEx waveFormatEx = (WaveFormatEx)Marshal.PtrToStructure(mediaType.formatPtr,typeof(WaveFormatEx));
				mediaInfo.Channels = waveFormatEx.nChannels;
				mediaInfo.SamplesPerSec = ((float)waveFormatEx.nSamplesPerSec) / 1000;
				mediaInfo.BitsPerSample = waveFormatEx.wBitsPerSample;
			}
		}

		private static void UpdateVideoPart(IMediaDet mediaDet,MediaInfo mediaInfo)
		{
			int hr = 0;
			AMMediaType mediaType = new AMMediaType();

			hr = mediaDet.get_StreamMediaType(mediaType);
			DsError.ThrowExceptionForHR(hr);

			mediaInfo.VideoSubType = mediaType.subType;

			double streamLength;
			hr = mediaDet.get_StreamLength(out streamLength);
			DsError.ThrowExceptionForHR(hr);

			mediaInfo.VideoLength = TimeSpan.FromSeconds(streamLength);

			if (mediaType.formatType == FormatType.VideoInfo)
			{
				VideoInfoHeader videoHeader = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.formatPtr,typeof(VideoInfoHeader));

				mediaInfo.ImageWidth = (short)videoHeader.BmiHeader.Width;
				mediaInfo.ImageHeight = (short)videoHeader.BmiHeader.Height;
				mediaInfo.BitsPerPixel = videoHeader.BmiHeader.BitCount;
				mediaInfo.FourCC = FourCCToString(videoHeader.BmiHeader.Compression);
			}
		}

		private static string FourCCToString(int fourcc)
		{
			byte[] bytes = new byte[4];

			bytes[0] = (byte)(fourcc & 0x000000ff);
			fourcc = fourcc >> 8;
			bytes[1] = (byte)(fourcc & 0x000000ff);
			fourcc = fourcc >> 8;
			bytes[2] = (byte)(fourcc & 0x000000ff);
			fourcc = fourcc >> 8;
			bytes[3] = (byte)(fourcc & 0x000000ff);

			return Encoding.ASCII.GetString(bytes);
		}

		private static Bitmap GetSnapshot(IMediaDet mediaDet,int width,int height,double position)
		{
			int hr = 0;
			Bitmap bitmap = null;
			int bufferSize = 0;
			IntPtr buffer = IntPtr.Zero;

			try
			{
				hr = mediaDet.GetBitmapBits(position, out bufferSize, IntPtr.Zero, width, height);
				if (hr == 0)
				{
					buffer = Marshal.AllocCoTaskMem(bufferSize);
					hr = mediaDet.GetBitmapBits(position, out bufferSize, buffer, width, height);
					DsError.ThrowExceptionForHR(hr);

					BitmapInfoHeader bitmapHeader = (BitmapInfoHeader)Marshal.PtrToStructure(buffer, typeof(BitmapInfoHeader));
					IntPtr bitmapData;

					if (IntPtr.Size == 4)
						bitmapData = new IntPtr(buffer.ToInt32() + bitmapHeader.Size);
					else
						bitmapData = new IntPtr(buffer.ToInt64() + bitmapHeader.Size);

					bitmap = new Bitmap(bitmapHeader.Width, bitmapHeader.Height, PixelFormat.Format24bppRgb);
					BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmapHeader.Width, bitmapHeader.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

					/*
					for (int i = 0; i < width * height * 3 ; i++)
					{
					  byte b = Marshal.ReadByte(bitmapData, i);
					  Marshal.WriteByte(bmpData.Scan0, i, b);
					}
					*/

					CopyMemory(bmpData.Scan0, bitmapData, width * height * 3);
					bitmap.UnlockBits(bmpData);

					bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
				}
			}
			catch
			{
				return null;
			}
			finally
			{
				if (buffer != IntPtr.Zero)
					Marshal.FreeCoTaskMem(buffer);
			}

			return bitmap;
		}

		[DllImport("kernel32.dll",EntryPoint = "RtlMoveMemory")]
		private static extern void CopyMemory(IntPtr Destination,IntPtr Source,int Length);
	}
}
